@namespace Oqtane.Modules.Controls
@using System.Threading
@inherits ModuleControlBase
@inject IFolderService FolderService
@inject IFileService FileService
@inject ISettingService SettingService
@inject IUserService UserService
@inject IStringLocalizer<FileManager> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer

@if (_initialized)
{
    <div id="@Id" class="container-fluid px-0">
        <div class="row">
            <div class="col">
                <div class="container-fluid px-0">
                    @if (ShowFolders)
                    {
                        <div class="row">
                            <div class="col">
                                <select class="form-select" value="@FolderId" @onchange="(e => FolderChanged(e))">
                                    <option value="-1">&lt;@Localizer["Folder.Select"]&gt;</option>
                                    @foreach (Folder folder in _folders)
                                    {
                                        <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
                                    }
                                </select>
                            </div>
                        </div>
                    }
                    @if (ShowFiles)
                    {
                        <div class="row mt-1">
                            <div class="col">
                                <select class="form-select" value="@FileId" @onchange="(e => FileChanged(e))">
                                    <option value="-1">&lt;@Localizer["File.Select"]&gt;</option>
                                    @foreach (File file in _files)
                                    {
                                        <option value="@(file.FileId)">@(file.Name)</option>
                                    }
                                </select>
                            </div>
                        </div>
                    }
                    else
                    {
                        if (FileId != -1 && _file != null && !UploadMultiple)
                        {
                            <input class="form-control" @bind="@_file.Name" disabled />
                        }
                    }
                    @if (ShowUpload && _haseditpermission)
                    {
                        <div class="row mt-2">
                            <div class="col">
                                @if (UploadMultiple)
                                {
                                    <input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
                                }
                                else
                                {
                                    <input type="file" id="@_fileinputid" name="file" accept="@_filter" />
                                }
                            </div>
                            <div class="col-auto">
                                <button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
                                @if (FileId != -1 && !UploadMultiple)
                                {
                                    <button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
                                }
                            </div>
                        </div>
                        @if (ShowProgress)
                        {
                            <div class="row">
                                <div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
                                <div class="col mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
                            </div>
                        }
                        else
                        {
                            if (_uploading)
                            {
                                <div class="app-progress-indicator"></div>
                            }
                        }
                    }
                    @if (!string.IsNullOrEmpty(_message))
                    {
                        <div class="row mt-1">
                            <div class="col">
                                <ModuleMessage Message="@_message" Type="@_messagetype" />
                            </div>
                        </div>
                    }
                </div>
            </div>
            @if (_image != string.Empty)
            {
                <div class="col-auto">
                    @((MarkupString) _image)
                </div>
            }
        </div>
    </div>
}

@code {
    private bool _initialized = false;
    private List<Folder> _folders;
    private List<File> _files = new List<File>();
    private string _fileinputid = string.Empty;
    private string _progressinfoid = string.Empty;
    private string _progressbarid = string.Empty;
    private string _filter = "*";
    private bool _haseditpermission = false;
    private string _image = string.Empty;
    private File _file = null;
    private string _guid;
    private string _message = string.Empty;
    private MessageType _messagetype;
    private bool _uploading = false;

    [Parameter]
    public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility

    [Parameter]
    public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid 

    [Parameter]
    public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path 

    [Parameter]
    public int FileId { get; set; } = -1; // optional - for selecting a specific file by default

    [Parameter]
    public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"

    [Parameter]
    public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true

    [Parameter]
    public bool ShowUpload { get; set; } = true; // optional - for indicating whether a Upload controls should be displayed - default is true

    [Parameter]
    public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true

    [Parameter]
    public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true

    [Parameter]
    public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true

    [Parameter]
    public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false

    [Parameter]
    public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false

    [Parameter]
    public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded

    [Parameter]
    public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected

    [Parameter]
    public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted

    protected override void OnInitialized()
    {
        // create unique id for component
        _guid = Guid.NewGuid().ToString("N");
        _fileinputid = "FileInput_" + _guid;
        _progressinfoid = "ProgressInfo_" + _guid;
        _progressbarid = "ProgressBar_" + _guid;
    }

    protected override async Task OnParametersSetAsync()
    {
        // packages folder is a framework folder for uploading installable nuget packages
        if (Folder == Constants.PackagesFolder)
        {
            ShowFiles = false;
            ShowFolders = false;
            Filter = "nupkg";
            ShowSuccess = true;
        }

        if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
        {
            Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
            if (folder != null)
            {
                FolderId = folder.FolderId;
            }
            else
            {
                FolderId = -1;
                _message = "Folder Path " + Folder + "Does Not Exist";
                _messagetype = MessageType.Error;
            }
        }

        if (ShowFolders)
        {
            _folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
        }
        else
        {
            if (FolderId != -1)
            {
                var folder = await FolderService.GetFolderAsync(FolderId);
                if (folder != null)
                {
                    _folders = new List<Folder> { folder };
                }
            }
        }

        if (FileId != -1)
        {
            File file = await FileService.GetFileAsync(FileId);
            if (file != null)
            {
                FolderId = file.FolderId;
            }
            else
            {
                FileId = -1; // file does not exist
                _message = "FileId " + FileId.ToString() + "Does Not Exist";
                _messagetype = MessageType.Error;
            }
        }

        await SetImage();

        if (!string.IsNullOrEmpty(Filter))
        {
            _filter = "." + Filter.Replace(",", ",.");
        }

        await GetFiles();

        _initialized = true;
    }

    private async Task GetFiles()
    {
        _haseditpermission = false;
        if (Folder == Constants.PackagesFolder)
        {
            _haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
            _files = new List<File>();
        }
        else
        {
            Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
            if (folder != null)
            {
                _haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
                if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
                {
                    _files = await FileService.GetFilesAsync(FolderId);
                }
                else
                {
                    _files = new List<File>();
                }
            }
            else
            {
                _haseditpermission = false;
                _files = new List<File>();
            }
            if (_filter != "*")
            {
                List<File> filtered = new List<File>();
                foreach (File file in _files)
                {
                    if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
                    {
                        filtered.Add(file);
                    }
                }
                _files = filtered;
            }
        }
    }

    private async Task FolderChanged(ChangeEventArgs e)
    {
        _message = string.Empty;
        try
        {
            FolderId = int.Parse((string)e.Value);
            await GetFiles();
            FileId = -1;
            _file = null;
            _image = string.Empty;
            StateHasChanged();
        }
        catch (Exception ex)
        {
            await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
            _message = Localizer["Error.File.Load"];
            _messagetype = MessageType.Error;
        }
    }

    private async Task FileChanged(ChangeEventArgs e)
    {
        _message = string.Empty;
        FileId = int.Parse((string)e.Value);
        await SetImage();
        await OnSelect.InvokeAsync(FileId);
        StateHasChanged();
    }

    private async Task SetImage()
    {
        _image = string.Empty;
        _file = null;
        if (FileId != -1)
        {
            _file = await FileService.GetFileAsync(FileId);
            if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
            {
                var maxwidth = 200;
                var maxheight = 200;

                var ratioX = (double)maxwidth / (double)_file.ImageWidth;
                var ratioY = (double)maxheight / (double)_file.ImageHeight;
                var ratio = ratioX < ratioY ? ratioX : ratioY;

                _image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
                            "\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
                            "\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
            }
        }
    }

    private async Task UploadFiles()
    {
        _message = string.Empty;
        var interop = new Interop(JSRuntime);
        var uploads = await interop.GetFiles(_fileinputid);

        if (uploads.Length > 0)
        {
            string restricted = "";
            foreach (var upload in uploads)
            {
                var filename = upload.Split(':')[0];
                var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
                if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
                {
                    restricted += (restricted == "" ? "" : ",") + extension;
                }
            }
            if (restricted == "")
            {
                try
                {
                    // upload the files
                    var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
                    var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
                    var jwt = "";
                    if (PageState.Runtime == Shared.Runtime.Hybrid)
                    {
                        jwt = await UserService.GetTokenAsync();
                        if (string.IsNullOrEmpty(jwt))
                        {
                            await logger.LogInformation("File Upload Failed From .NET MAUI Due To Missing Security Token. Token Options Must Be Set In User Settings.");
                            _message = "Security Token Not Specified";
                            _messagetype = MessageType.Error;
                            return;
                        }
                    }

                    if (!ShowProgress)
                    {
                        _uploading = true;
                        StateHasChanged();
                    }

                    await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);

                    // uploading is asynchronous so we need to poll to determine if uploads are completed
                    var success = true;
                    int upload = 0;
                    while (upload < uploads.Length && success)
                    {
                        success = false;
                        var filename = uploads[upload].Split(':')[0];

                        var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
                        var megabits = (size / 1048576.0) * 8; // binary conversion
                        var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload
                        var uploadtime = (megabits / uploadspeed); // seconds
                        var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
                        var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds

                        int attempts = 0;
                        while (attempts < maxattempts && !success)
                        {
                            attempts += 1;
                            Thread.Sleep(sleep);

                            if (Folder == Constants.PackagesFolder)
                            {
                                var files = await FileService.GetFilesAsync(folder);
                                if (files != null && files.Any(item => item.Name == filename))
                                {
                                    success = true;
                                }
                            }
                            else
                            {
                                var file = await FileService.GetFileAsync(int.Parse(folder), filename);
                                if (file != null)
                                {
                                    success = true;
                                }
                            }
                        }
                        if (success)
                        {
                            upload++;
                        }
                    }

                    // reset progress indicators
                    if (ShowProgress)
                    {
                        await interop.SetElementAttribute(_progressinfoid, "style", "display: none;");
                        await interop.SetElementAttribute(_progressbarid, "style", "display: none;");
                    }
                    else
                    {
                        _uploading = false;
                        StateHasChanged();
                    }

                    if (success)
                    {
                        await logger.LogInformation("File Upload Succeeded {Files}", uploads);
                        if (ShowSuccess)
                        {
                            _message = Localizer["Success.File.Upload"];
                            _messagetype = MessageType.Success;
                        }
                    }
                    else
                    {
                        await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
                        _message = Localizer["Error.File.Upload"];
                        _messagetype = MessageType.Error;
                    }

                    if (Folder == Constants.PackagesFolder)
                    {
                        await OnUpload.InvokeAsync(-1);
                    }
                    else
                    {
                        // set FileId to first file in upload collection
                        var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
                        if (file != null)
                        {
                            FileId = file.FileId;
                            await SetImage();
                            await OnSelect.InvokeAsync(FileId);
                            await OnUpload.InvokeAsync(FileId);
                        }
                        await GetFiles();
                        StateHasChanged();
                    }
                }
                catch (Exception ex)
                {
                    await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
                    _message = Localizer["Error.File.Upload"];
                    _messagetype = MessageType.Error;
                    _uploading = false;
                }

            }
            else
            {
                _message = string.Format(Localizer["Message.File.Restricted"], restricted);
                _messagetype = MessageType.Warning;
            }
        }
        else
        {
            _message = Localizer["Message.File.NotSelected"];
            _messagetype = MessageType.Warning;
        }
    }

    private async Task DeleteFile()
    {
        _message = string.Empty;
        try
        {
            await FileService.DeleteFileAsync(FileId);
            await logger.LogInformation("File Deleted {File}", FileId);
            await OnDelete.InvokeAsync(FileId);

            if (ShowSuccess)
            {
                _message = Localizer["Success.File.Delete"];
                _messagetype = MessageType.Success;
            }

			await GetFiles();
			FileId = -1;
			await SetImage();
            await OnSelect.InvokeAsync(FileId);
            StateHasChanged();
		}
		catch (Exception ex)
		{
			await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);

			_message = Localizer["Error.File.Delete"];
			_messagetype = MessageType.Error;
		}
	}

	public int GetFileId() => FileId;

	public int GetFolderId() => FolderId;

	public File GetFile() => _file;

	public async Task Refresh()
	{
		await Refresh(-1);
	}

	public async Task Refresh(int fileId)
	{
		await GetFiles();
		if (fileId != -1)
		{
			var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
			if (file != null)
			{
				FileId = file.FileId;
				await SetImage();
			}
		}
		StateHasChanged();
	}
}
